package com.weiss.jpro.utils;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;

@Component
public class RedisIdWorker {

    // 全局唯一id生成策略：
    // UUID：利用jdk自带的uuid工具类就能生成，
    // 生成的是16进制的一长串字符，返回的结果是字符串结构，并且不是单调递增，最然可以作为唯一id，但是不够友好
    // redis自增：整体单调递增，数值长度不大，不超过long，为数字类型，存储在数据库占用空间相对比较小
    // snowflake算法：long类型，64位数字，自增采用的当前机器自增，内部维护，需要维护一个机器id，对时钟依赖比较高，如果时间不准确会出现一些异常问题
    // 数据库自增：单独建立一张表用来自增id，即用数据库的一张表代替redis自增，将自增id存储在数据库中，但是性能不如redis

    // redis自增id策略：
    // ID构造是 时间戳 + 计数器, 每天一个key，方便统计订单量，还可以限定自增的值，防止超过自增上限

    // 全局id生成器：
    // 每个店铺都可以发优惠券，当用户抢购时，就会生成订单并保存到tb_voucher_order表中，而订单如果使用数据库自增id就存在一些问题：
    // id的规律太明显，用户就能根据id猜测信息，如今天下一单，明天下一单，就能猜测一天包含多少订单
    // 受到单表数据量限制，订单的一个特点就是数据量比较大，用户只要产生购买的行为就会不断产生新的订单
    // 如果网站做到一定规模，用户量达到了数百万，这样每天都会产生数十万近百万的订单，这样一年下来可能会达到数千万的订单，两年三年四年会达到数亿数十亿订单
    // 单张表不能保存如此多的数据，无法保存就得分到多张表，每张表都采用自增长就会出现重复id，而订单id不应该重复

    // 全局id生成器，是一种在分布式系统下用来生成全局唯一id的工具，一般要满足下列特性：
    // 唯一性：很多业务必须要求唯一，如订单
    // 高可用：任何时间都能生成正确的id，不能来找你生成id你挂了，影响其他业务
    // 高性能：还要保证生成的速度足够快，不能说生成一个id得几秒钟，拖慢其他业务
    // 递增性：具备单调递增的特性，是用来替代数据库id的，不一定是规律递增，但得保证整体增大的特性，有利于数据库创建索引，提高插入时的速度
    // 安全性：规律性不能太明显，让别人能猜测其他订单id

    // 开始时间戳
    private static final long BEGIN_TIMESTAMP = 1735689600L; // 由main函数打印得到
    // 序列号的位数
    private static final long COUNT_BITS = 32;

    private StringRedisTemplate stringRedisTemplate;

    public RedisIdWorker(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    public long nextId(String keyPrefix) {
        // id:long类型 1符号位 31位时间戳 32位序列号
        // 1.生成时间戳
        // 当前时间减去开始时间戳得到秒数
        LocalDateTime now = LocalDateTime.now();
        long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
        long timeStamp = nowSecond - BEGIN_TIMESTAMP;

        // 2.生成序列号
        // redis自增上限为2^64，序列号32位，有增长上限，在key后面拼接日期字符串，代表当天增长，避免长时间增加达到上限
        // 2.1 获取当前日期，精确到天，用冒号分隔开，在redis里面会有层级，方便统计不同粒度，按月、按天
        String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
        // 2.2 自增长 ,s使用基本类型，不用包装类，后续还有拼接运算
        long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);
//        stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + "20250331");

        // 3.拼接并返回
        // 时间戳通过位运算（左移）空出序列号位置，通过或运算拼接
        return timeStamp << COUNT_BITS | count;
    }

    public static void main(String[] args) {
        LocalDateTime time = LocalDateTime.of(2025, 1, 1, 0, 0, 0);
        long second = time.toEpochSecond(ZoneOffset.UTC); // 得到具体秒数，传入时区
        System.out.println("second = " + second);
    }
}
